/**
 * @author YJSYS
 *
 */

package com.qd.extcam;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Base64;
import android.util.Log;

public class ClientJob extends Thread {
	private static final int PRT_POS_DATA = 7;
	private static final int PRT_POS_COMMAND = 2;
	// private static final int PRT_POS_RESULT = 1;
	private static final int PRT_POS_LEN = 3;

	private static final byte SUCCESS = 0;
	private static final byte FAIL = 1;

	private ExtCamActivity activity = null;
	private Socket client = null;
	private ServerAsyncTask server = null;
	private byte[] queue = null;
	private int qIndex = 0;

	private InputStream is;
	private OutputStream os;

	public boolean running;

	enum Command {
		START_RECORD, STOP_RECORD, GET_DATA, GET_FILE_LIST, GET_FILE, DELETE_FILE, LOGOUT, GET_STATUS
	}

	/** 클라이언트 작업 쓰레드 생성자
	 * @param activity 메인 Activity
	 * @param socket 크랑이언트 소켓
	 * @param server 서버 작업 쓰레드
	 */
	public ClientJob(ExtCamActivity activity, Socket socket, ServerAsyncTask server) {
		this.activity = activity;
		this.client = socket;
		this.server = server;
		this.queue = new byte[102400];
		this.qIndex = 0;

		this.running = true;
	}

	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	@Override
	public void run() {
		try {
			is = client.getInputStream();
			os = client.getOutputStream();
			byte[] buffer = new byte[256];
			int receivedLen = 0;
			while (running) {
				receivedLen = is.read(buffer);
				if (receivedLen > 0) {
					addQueue(buffer, receivedLen);
					doResponse();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		Log.e(ExtCamActivity.TAG, "ClientJob Ended");
	}

	/** 클라이언트로 부터 오는 요청을 MessageQueue에 추가한다.
	 * @param buff 클라이언트 요청
	 * @param nRcvdLen 클라이언트 요청 길이
	 */
	private void addQueue(byte[] buff, int nRcvdLen) {
		System.arraycopy(buff, 0, queue, qIndex, nRcvdLen);
		qIndex += nRcvdLen;
	}

	/** 처리된 요청을 MessageQueue에서 제거한다.
	 * @param nPackLen 처리된 요청 길이
	 */
	private void removeQueue(int nPackLen) {
		System.arraycopy(queue, nPackLen, queue, 0, qIndex - nPackLen);
		qIndex -= nPackLen;
	}

	/** 클라이언트에 처리 결과를 보낸다.
	 * @param packet 클라이언트에 보낼 처리 결과 패킷
	 */
	private void sendPacket(Packet packet) {
		try {
			this.os.write(packet.toBytes());
			this.os.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/** 처리결과를 통신을 위한 패킷 형태로 만들어 반환한다.
	 * @param command 클라이언트 요청 구분
	 * @param data 처리 결과 데이터
	 * @param rslt 처리 결과 코드 (0: 성공 1: 실패)
	 * @return 처리결과를 패킷형태로 반환
	 */
	private Packet makePacket(Command command, byte[] data, byte rslt) {
		int dataLen = 0;
		if (data != null)
			dataLen = data.length;

		Packet packet = new Packet();
		packet.rslt = rslt;
		packet.command = (byte)command.ordinal();
		packet.len = dataLen;
		packet.data = data;
		return packet;
	}

	/** 클라이언트에서 로그아웃 요청에 대한 처리
	 * 
	 */
	private void doLogout() {
		this.running = false;
		Packet packet = makePacket(Command.LOGOUT, null, SUCCESS);
		sendPacket(packet);
		if (server != null) {
			synchronized (server.jobList) {
				server.jobList.remove(this);
			}
		}
	}

	/** 클라이언트에서 녹화시작 요청에 대한 처리
	 * @param data 녹화 파일 명
	 */
	private void doStartRecord(byte[] data) {
		if (this.activity != null) {
			Packet packet = null;
			long freeSize = this.activity.getAvailableExternalMemorySize();
			Log.e(ExtCamActivity.TAG, "getAvailableExternalMemorySize() = " + (long)freeSize + ", " + ExtCamActivity.MIN_FREE_MEMORY_SIZE);
			if (ExtCamActivity.MIN_FREE_MEMORY_SIZE > freeSize) {
				String strSize = Long.toString(freeSize);
				packet = makePacket(Command.START_RECORD, strSize.getBytes(), FAIL);
			} else {
				this.activity.startRecording(new String(data));
				packet = makePacket(Command.START_RECORD, null, SUCCESS);
			}
			sendPacket(packet);
		}
	}

	/** 클라이언트에서 녹화중지 요청에 대한 처리
	 * 
	 */
	private void doStopRecord() {
		if (this.activity != null) {
			this.activity.stopRecording();
			Packet packet = makePacket(Command.STOP_RECORD, null, SUCCESS);
			sendPacket(packet);
		}
	}

	/** 클라이언트에서 파일 요청에 대한 처리
	 * @param data 요청 파일명
	 */
	private void doGetFile(byte[] data) {
		String fileName = new String(data);
		Log.e(ExtCamActivity.TAG, "doGetFile(" + fileName + ")");
		File file = new File(ExtCamActivity.EXTCAM_DIR, fileName);
		if (file.exists()) {
			try {
				Log.e(ExtCamActivity.TAG, "doGetFile() Start +++");
				long sendLength = 0;

				// send file header
				String fileInfo = fileName + "##" + file.length(); 
				int fileInfoLen = fileInfo.getBytes().length;
				byte[] header = new byte[1 + fileInfoLen];
				header[0] = 1; // start
				System.arraycopy(fileInfo.getBytes(), 0, header, 1, fileInfoLen);
				sendPacket(makePacket(Command.GET_FILE, header, SUCCESS));
				sendLength += header.length;

				// send file data
				byte[] buffer = new byte[1 + 4096];
				buffer[0] = 2; // data
				FileInputStream fis = new FileInputStream(file);
				int readLen = 0;
				while ((readLen = fis.read(buffer, 1, (buffer.length-1))) > 0) {
					if (readLen == (buffer.length-1)) {
						sendPacket(makePacket(Command.GET_FILE, buffer, SUCCESS));
						sendLength += buffer.length;
					} else if (readLen < (buffer.length-1)) {
						byte[] lastBuffer = new byte[readLen + 1];
						lastBuffer[0] = 2;	// data
						System.arraycopy(buffer, 1, lastBuffer, 1, readLen);
						sendPacket(makePacket(Command.GET_FILE, lastBuffer, SUCCESS));
						sendLength += lastBuffer.length;
					}
				}
				fis.close();

				// send file tail
				byte[] tail = new byte[1];
				tail[0] = 3; // end
				sendPacket(makePacket(Command.GET_FILE, tail, SUCCESS));
				sendLength += tail.length;

				Log.e(ExtCamActivity.TAG, "doGetFile() End " + sendLength + "---");
			} catch (FileNotFoundException e) {
				Log.e(ExtCamActivity.TAG, "doGetFile() FileNotFoundException");
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(ExtCamActivity.TAG, "doGetFile() IOException");
				e.printStackTrace();
			}
		}
	}

	/** 클라이언트에서 녹화중 프레인, 센서, 위치정보 요청에 대한 처리
	 * 
	 */
	private void doGetData() {
		String jsonString = "";
		JSONObject json = new JSONObject();
		try {
			json.put("status", this.activity.mIsStartLog);
			if (this.activity.mIsStartLog) {
				json.put("azimuth", this.activity.getAzimuth());
				json.put("pitch", this.activity.getPitch());
				json.put("roll", this.activity.getRoll());
				json.put("accel_1", this.activity.getAccels(0));
				json.put("accel_2", this.activity.getAccels(1));
				json.put("accel_3", this.activity.getAccels(2));
				json.put("latitude", this.activity.getLatitude());
				json.put("logitude", this.activity.getLongitude());
				json.put("altitude", this.activity.getAltitude());
				json.put("speed", this.activity.getSpeed());
				json.put("utctime", this.activity.getUtcTime());
				json.put("frame", Base64.encodeToString(this.activity.getJpegFrameBuffer("frame.jpg"), Base64.DEFAULT));
			}
			jsonString = json.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		Packet packet = makePacket(Command.GET_DATA, jsonString.getBytes(), SUCCESS);
		sendPacket(packet);
	}

	/** 클라이언트에서 파일 삭제 요청에 대한 처리
	 * @param data 삭제 파일 목록 
	 */
	private void doDeleteFile(byte[] data) {
		String fileName = new String(data);
		String[] fileNames = fileName.split(",");
		if (fileNames.length > 0) {
			for (int i = 0; i < fileNames.length; i++) {
				File file = new File(ExtCamActivity.EXTCAM_DIR, fileNames[i]);
				if (file.exists())
					file.delete();
			}
		}

		Packet packet = makePacket(Command.DELETE_FILE, data, SUCCESS);
		sendPacket(packet);
	}

	/** 클라이언트에서 모든 파일 삭제 요청에 대한 처리
	 * 
	 */
	private void doDeleteFileAll() {
		File file = new File(ExtCamActivity.EXTCAM_DIR);
		File[] files = file.listFiles();
		if (files == null)
			return;

		for (int i = 0; i < files.length; i++) {
			if (!files[i].isDirectory()) {
				files[i].delete();
			}
		}

		Packet packet = makePacket(Command.DELETE_FILE, null, SUCCESS);
		sendPacket(packet);
	}

	/** 클라이언트에서 파일 목록 요청에 대한 처리
	 * 
	 */
	private void doGetFileList() {
		File file = new File(ExtCamActivity.EXTCAM_DIR);
		File[] files = file.listFiles();
		if (files == null)
			return;

		ArrayList<String> fileList = new ArrayList<String>();
		for (int i = 0; i < files.length; i++) {
			if (!files[i].isDirectory()) {
				fileList.add(files[i].getName() + "##" + files[i].length());
			}
		}
		Collections.sort(fileList);
		String strJson = "";
		try {
			JSONObject json = new JSONObject();
			json.put("count", fileList.size());
			JSONArray jFiles = new JSONArray();
			json.put("file_list", jFiles);
			for (int i = 0; i < fileList.size(); i++) {
				JSONObject jFile = new JSONObject();
				jFile.put("file", fileList.get(i));
				jFiles.put(jFile);
			}
			strJson = json.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		Packet packet = makePacket(Command.GET_FILE_LIST, strJson.getBytes(), SUCCESS);
		sendPacket(packet);
	}

	private void doGetStatus() {
		if (this.activity != null) {
			byte[] status = new byte [1];
			if (this.activity.mIsStartLog) {
				status[0] = 1;
			} else {
				status[0] = 0;
			}
			Packet packet = makePacket(Command.GET_STATUS, status, SUCCESS);
			sendPacket(packet);
		}
	}

	/**
	 * 클라이언트의 요청을 대기하면서
	 * 요청이 있을시 MessageQueue에 추가하고 처리한다.
	 */
	private void doResponse() {
		byte[] dataBuf = null;
		short nDataLen = 0;
		for (;;) {
			if (qIndex <= PRT_POS_DATA)
				break;

			int nPackLen = (queue[PRT_POS_LEN] << 24)
					+ ((queue[PRT_POS_LEN + 1] & 0xff) << 16)
					+ ((queue[PRT_POS_LEN + 2] & 0xff) << 8)
					+ (queue[PRT_POS_LEN + 3] & 0xFF);
			nPackLen += 8;
			if (nPackLen > qIndex)
				break;

			Command cmd = Command.values()[queue[PRT_POS_COMMAND]];
			if (queue[PRT_POS_DATA] != (byte) 0xf2) {
				nDataLen = (short) (nPackLen - 8);
				dataBuf = new byte[nDataLen];
				System.arraycopy(queue, PRT_POS_DATA, dataBuf, 0, nDataLen);
			}

			removeQueue(nPackLen);

			switch (cmd) {
			case START_RECORD: {
				doStartRecord(dataBuf);
				break;
			}
			case STOP_RECORD: {
				doStopRecord();
				break;
			}
			case GET_FILE_LIST: {
				doGetFileList();
				break;
			}
			case GET_DATA: {
				doGetData();
				break;
			}
			case GET_FILE: {
				doGetFile(dataBuf);
				break;
			}
			case DELETE_FILE: {
				if (nDataLen == 0)
					doDeleteFileAll();
				else
					doDeleteFile(dataBuf);
				break;
			}
			case LOGOUT: {
				doLogout();
				break;
			}
			case GET_STATUS: {
				doGetStatus();
				break;
			}
			}
		}
	}
}

class Packet {
	public byte header = (byte) 0xf1;
	public byte rslt;
	public byte command;
	public int len;
	public byte[] data;
	public byte tail = (byte) 0xf2;

	/** 
	 * @return 패킷 정보를 바이트 형태로 반환한다.
	 */
	public byte[] toBytes() {
		int buffer_len = 8 + len;
		byte[] buffer = new byte[8 + len];
		buffer[0] = header;
		buffer[1] = rslt;
		buffer[2] = command;
		buffer[3] = (byte) ((len >>> 24) & 0xFF);
		buffer[4] = (byte) ((len >>> 16) & 0xFF);
		buffer[5] = (byte) ((len >>>  8) & 0xFF);
		buffer[6] = (byte) ((len >>>  0) & 0xFF);
		if (data != null)
			System.arraycopy(data, 0, buffer, 7, len);
		buffer[buffer_len - 1] = tail;
		return buffer;
	}
}
